package com.bs.elasticsearch.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.bs.common.utils.SnowFlakeUtil;
import com.bs.elasticsearch.mapper.ElasticSearchMapper;
import com.bs.elasticsearch.service.ElasticSearchService;
import com.bs.elasticsearch.vo.GoodsVo;
import com.bs.elasticsearch.vo.LocationVo;
import lombok.extern.log4j.Log4j2;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.metrics.ParsedAvg;
import org.elasticsearch.search.aggregations.metrics.ParsedMax;
import org.elasticsearch.search.aggregations.metrics.ParsedMin;
import org.elasticsearch.search.aggregations.metrics.ParsedSum;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.*;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.*;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author bingshao
 * @date 2023/3/17
 **/
@Log4j2
@Service
public class ElasticSearchServiceImpl implements ElasticSearchService {

    @Resource
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    @Resource
    private ElasticSearchMapper elasticSearchDao;


    @Override
    public void createIndex() {
        IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(GoodsVo.class);
        indexOperations.create();
        Document mapping = indexOperations.createMapping();
        indexOperations.putMapping(mapping);
        indexOperations.addAlias(new AliasQuery("goods_vo"));
    }

    @Override
    public void save() {
        GoodsVo goodsVo1 = GoodsVo.builder().id(SnowFlakeUtil.getId())
                .name("苹果手机")
                .salesVolume(2000)
                .price(9998D)
                .description("最新款苹果手机，phone 15")
                .status(1)
                .updateTime(new Date())
                .catalog1("手机")
                .catalog2("电子产品").build();

        GoodsVo goodsVo2 = GoodsVo.builder().id(SnowFlakeUtil.getId())
                .name("vivo 手机")
                .salesVolume(3500)
                .price(3600D)
                .description("vivo x90,最新款x系列")
                .status(1)
                .updateTime(new Date())
                .catalog1("手机")
                .catalog2("电子产品").build();

        elasticsearchRestTemplate.save(goodsVo1, goodsVo2);
    }

    @Override
    public void batchSave() {
        GoodsVo goodsVo1 = GoodsVo.builder().id(SnowFlakeUtil.getId())
                .name("OPPO手机")
                .salesVolume(1500)
                .price(2300D)
                .description("OPPO 最新款")
                .status(1)
                .updateTime(new Date())
                .catalog1("手机")
                .catalog2("电子产品").build();

        GoodsVo goodsVo2 = GoodsVo.builder().id(SnowFlakeUtil.getId())
                .name("魅族手机")
                .salesVolume(2000)
                .price(2000D)
                .description("魅族手机全新系列")
                .status(1)
                .updateTime(new Date())
                .catalog1("手机")
                .catalog2("电子产品").build();
        List<GoodsVo> goodsVoList = new ArrayList<>();
        goodsVoList.add(goodsVo1);
        goodsVoList.add(goodsVo2);
        elasticsearchRestTemplate.save(goodsVoList);
    }

    @Override
    public void update() {
        // 价格从9998改成9999
        GoodsVo goodsVo1 = GoodsVo.builder().id(1637621497344372736L)
                .name("苹果手机")
                .salesVolume(2000)
                .price(9999D)
                .description("最新款苹果手机，phone 15")
                .status(1)
                .updateTime(new Date())
                .catalog1("手机")
                .catalog2("电子产品").build();
        UpdateQuery build = UpdateQuery.builder(String.valueOf(goodsVo1.getId())).withDocument(Document.parse(JSON.toJSONString(goodsVo1))).build();
        elasticsearchRestTemplate.update(build, elasticsearchRestTemplate.getIndexCoordinatesFor(GoodsVo.class));
    }

    @Override
    public void batchUpdate() {
        // 以下销量全部加上100进行批量修改测试
        GoodsVo goodsVo1 = GoodsVo.builder().id(1637621497344372737L)
                .name("vivo 手机")
                .salesVolume(3600)
                .price(3600D)
                .description("vivo x90,最新款x系列")
                .status(1)
                .updateTime(new Date())
                .catalog1("手机")
                .catalog2("电子产品").build();

        GoodsVo goodsVo2 = GoodsVo.builder().id(1637629811142766592L)
                .name("OPPO手机")
                .salesVolume(1600)
                .price(2300D)
                .description("OPPO 最新款")
                .status(1)
                .updateTime(new Date())
                .catalog1("手机")
                .catalog2("电子产品").build();

        GoodsVo goodsVo3 = GoodsVo.builder().id(1637629811142766593L)
                .name("魅族手机")
                .salesVolume(2100)
                .price(2000D)
                .description("魅族手机全新系列")
                .status(1)
                .updateTime(new Date())
                .catalog1("手机")
                .catalog2("电子产品").build();

        List<GoodsVo> goodsVoList = new ArrayList<>();
        goodsVoList.add(goodsVo1);
        goodsVoList.add(goodsVo2);
        goodsVoList.add(goodsVo3);

        List<UpdateQuery> updateQueryList = new ArrayList<>();
        goodsVoList.forEach(goodsVo -> {
            UpdateQuery build = UpdateQuery.builder(String.valueOf(goodsVo.getId())).withDocument(Document.parse(JSON.toJSONString(goodsVo))).build();
            updateQueryList.add(build);
        });

        elasticsearchRestTemplate.bulkUpdate(updateQueryList, elasticsearchRestTemplate.getIndexCoordinatesFor(GoodsVo.class));
    }

    @Override
    public void search() {
//        GoodsVo goodsVo = elasticsearchRestTemplate.get(String.valueOf(1637621497344372736L), GoodsVo.class);
//        log.info("根据id查询单个对象=======>{}", goodsVo);
//
//        SearchHits<GoodsVo> searchHits1 = elasticsearchRestTemplate.search(new NativeSearchQueryBuilder()
//                .withQuery(QueryBuilders.matchAllQuery()).withPageable(PageRequest.of(0, 5)).build(), GoodsVo.class);
//        searchHits1.forEach(searchHit -> log.info("分页查询所有结果=========>{}", searchHit.getContent()));
//
//        SearchHits<GoodsVo> searchHits2 = elasticsearchRestTemplate.search(new NativeSearchQueryBuilder()
//                .withQuery(QueryBuilders.matchQuery("name", "手机"))
//                .withHighlightFields(new HighlightBuilder.Field("name").preTags("<font style='color:red'>").postTags("</font>")).build(), GoodsVo.class);
//        List<GoodsVo> goodsVoList = new ArrayList<>();
//        searchHits2.forEach(searchHit -> {
//            log.info("单字段条件高亮查询=========>{}", searchHit.getContent());
//            GoodsVo goodsVo1 = GoodsVo.builder().build();
//            BeanUtil.copyProperties(searchHit.getContent(), goodsVo1);
//            Map<String, List<String>> highlightFields = searchHit.getHighlightFields();
//            if (CollectionUtil.isNotEmpty(highlightFields)) {
//                highlightFields.forEach((key, value) -> {
//                    goodsVo1.setName(value.get(0));
//                    goodsVoList.add(goodsVo1);
//                    log.info("高亮字段=========>{}", value);
//                });
//            }
//        });
//        if (CollectionUtil.isNotEmpty(goodsVoList)) {
//            goodsVoList.forEach(System.out::println);
//        }


////         QueryBuilders.termQuery如果匹配字符串，需要添加.keyword
//        SearchHits<GoodsVo> searchHits3 = elasticsearchRestTemplate.search(new NativeSearchQueryBuilder().withQuery(QueryBuilders.termQuery("catalog2.keyword", "电子产品")).build(), GoodsVo.class);
//        searchHits3.forEach(searchHit -> log.info("精确查找==========={}", searchHit.getContent()));
//
//        SearchHits<GoodsVo> searchHits4 = elasticsearchRestTemplate.search(new NativeSearchQueryBuilder().withQuery(QueryBuilders.matchQuery("catalog2", "电子产品")).build(), GoodsVo.class);
//        searchHits4.forEach(searchHit -> log.info("查找关键字==========={}", searchHit.getContent()));
//
//        SearchHits<GoodsVo> searchHits5 = elasticsearchRestTemplate.search(new NativeSearchQueryBuilder().withQuery(QueryBuilders.matchPhraseQuery("catalog2", "电子产品")).build(), GoodsVo.class);
//        searchHits5.forEach(searchHit -> log.info("matchPhrase==========={}", searchHit.getContent()));

//        SearchHits<GoodsVo> searchHits6 = elasticsearchRestTemplate.search(new NativeSearchQueryBuilder().withQuery(QueryBuilders.rangeQuery("salesVolume").from(1600).to(3600)).build(), GoodsVo.class);
//        searchHits6.forEach(searchHit -> log.info("销售量区间默认包含边界值======{}", searchHit.getContent()));
//
//        SearchHits<GoodsVo> searchHits7 = elasticsearchRestTemplate.search(new NativeSearchQueryBuilder().withQuery(QueryBuilders.rangeQuery("salesVolume").from(1600).to(3600).includeLower(false).includeUpper(false)).build(), GoodsVo.class);
//        searchHits7.forEach(searchHit -> log.info("销售量区间不包含边界值======{}", searchHit.getContent()));
//
//        // 加了排序
//        SearchHits<GoodsVo> searchHits8 = elasticsearchRestTemplate.search(new NativeSearchQueryBuilder().withQuery(QueryBuilders.rangeQuery("salesVolume").gte(1600).lte(3600)).withSort(SortBuilders.fieldSort("salesVolume").order(SortOrder.DESC)).build(), GoodsVo.class);
//        searchHits8.forEach(searchHit -> log.info("销售量区间包含边界值降序排序======{}", searchHit.getContent()));
//
//        SearchHits<GoodsVo> searchHits9 = elasticsearchRestTemplate.search(new NativeSearchQueryBuilder().withQuery(QueryBuilders.rangeQuery("salesVolume").gt(1600).lt(3600)).build(), GoodsVo.class);
//        searchHits9.forEach(searchHit -> log.info("销售量区间不包含边界值======{}", searchHit.getContent()));


//        // 以什么为结尾
//        elasticsearchRestTemplate.search(new NativeSearchQueryBuilder().withQuery(QueryBuilders.fuzzyQuery("","")).build(), GoodsVo.class);
//
//        // 以什么为开头
//        elasticsearchRestTemplate.search(new NativeSearchQueryBuilder().withQuery(QueryBuilders.prefixQuery("","")).build(), GoodsVo.class);
    }

    @Override
    public void searchScroll() {
        // 模仿分页取第4页的1条数据
        int pageNo = 4;
        int pageSize = 1;

        List<String> scrollIds = new ArrayList<>();
        // 缓存第一页符合搜索条件的数据
        SearchScrollHits<GoodsVo> searchScrollHits = elasticsearchRestTemplate.searchScrollStart(60 * 1000, new NativeSearchQueryBuilder().withQuery(QueryBuilders.matchAllQuery()).withPageable(PageRequest.of(0, pageSize)).build(), GoodsVo.class, IndexCoordinates.of("goods"));
        String scrollId = searchScrollHits.getScrollId();
        int scrollTime = 1;
        while (searchScrollHits.hasSearchHits() && scrollTime < pageNo) {
            // 根据上次搜索结果scroll_id进入下一页数据搜索
            searchScrollHits = elasticsearchRestTemplate.searchScrollContinue(scrollId, 60 * 1000, GoodsVo.class, IndexCoordinates.of("goods"));//该方法执行后会重新刷新快照保留时间
            scrollId = searchScrollHits.getScrollId();
            scrollTime = scrollTime + 1;
            scrollIds.add(scrollId);
        }

        // 清除 scroll
        elasticsearchRestTemplate.searchScrollClear(scrollIds);

        // 从缓存中读取数据
        for (SearchHit<GoodsVo> searchHit : searchScrollHits.getSearchHits()) {
            GoodsVo goods = searchHit.getContent();
            log.info(goods);
        }
    }

    @Override
    public void aggMetric() {
        SearchHits<GoodsVo> searchHits = elasticsearchRestTemplate.search(new NativeSearchQueryBuilder()
                .addAggregation(AggregationBuilders.max("maxPrice").field("price"))
                .addAggregation(AggregationBuilders.min("minPrice").field("price"))
                .addAggregation(AggregationBuilders.avg("avgPrice").field("price"))
                .addAggregation(AggregationBuilders.sum("sumPrice").field("price"))
                .withSourceFilter(new FetchSourceFilterBuilder().build())
                .build(), GoodsVo.class, IndexCoordinates.of("goods"));
        Aggregations aggregations = searchHits.getAggregations();
        ParsedMax maxPrice = aggregations.get("maxPrice");
        log.info("最贵的价格为====={}", maxPrice.getValue());
        ParsedMin minPrice = aggregations.get("minPrice");
        log.info("最便宜的价格为====={}", minPrice.getValue());
        ParsedAvg parsedAvg = aggregations.get("avgPrice");
        log.info("平均价格为====={}", parsedAvg.getValue());
        ParsedSum parsedSum = aggregations.get("sumPrice");
        log.info("总价格为====={}", parsedSum.getValue());
    }

    @Override
    public void aggBucket() {
        SearchHits<GoodsVo> goodsVoSearchHits = elasticsearchRestTemplate.search(new NativeSearchQueryBuilder()
                // keyword类型需要加.keyword
                .addAggregation(AggregationBuilders.terms("aggCatalog1").field("catalog1.keyword").size(2))
                .withSourceFilter(new FetchSourceFilterBuilder().build())
                .build(), GoodsVo.class, IndexCoordinates.of("goods"));
        Aggregations aggregations = goodsVoSearchHits.getAggregations();
        ParsedStringTerms aggCatalog1 = aggregations.get("aggCatalog1");
        //手机,4
        aggCatalog1.getBuckets().forEach(bucket -> log.info(bucket.getKeyAsString() + "," + bucket.getDocCount()));
    }
}
